﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MiniCPU.Entities;
using MiniCPU.SimpleCPU;

namespace MiniCPU
{
    /// <summary>
    /// CPU entity demonstrated in the VHDL task.
    /// </summary>
    public class CPU
    {
        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="CPU"/> class.
        /// </summary>
        public CPU()
        {
            this.AR = new Register(6, "AR");
            this.PC = new Register(6, "PC");
            this.DR = new Register(8, "DR");
            this.AC = new Register(8, "AC");
            this.IR = new Register(8, "IR");
            this.SequenceCounter = new Register(3, "SC");
            this.Bus = new Bus(8, 8);

            // Memory
            this.Memory = new Memory(8, this.AR);

            // Connect ALU
            this.ALU = new ALU(this.AC, this.DR, 8);

            // Sequence Counter
            this.SequenceCounter.IntValue = -1;
            this.SequenceCounter.Increment.Value = true;

            // Connect Bus Inputs
            this.Bus.ConnectComponent((int)BusLocations.Memory, this.Memory);
            this.Bus.ConnectComponent((int)BusLocations.AR, this.AR);
            this.Bus.ConnectComponent((int)BusLocations.PC, this.PC);
            this.Bus.ConnectComponent((int)BusLocations.DR, this.DR);
            this.Bus.ConnectComponent((int)BusLocations.AC, this.AC);
            this.Bus.ConnectComponent((int)BusLocations.IR, this.IR);
            
            // Connect Register Inputs
            this.Memory.DataOnInput = this.Bus;
            this.Memory.Address = this.AR;
            this.AR.DataOnLoad = this.Bus;
            this.PC.DataOnLoad = this.Bus;
            this.DR.DataOnLoad = this.Bus;
            this.IR.DataOnLoad = this.Bus;
            this.AC.DataOnLoad = this.ALU;

            // ControlUnit
            this.ControlUnit = new ControlUnit(this.SequenceCounter, this.IR);

            // Attach Control Signals
            this.AttachControlSignals();
        }
        #endregion

        #region Enumerations
        /// <summary>
        /// Enumeration of Bus Locations in the CPU.
        /// </summary>
        public enum BusLocations
        {
            AR = 1,
            PC = 2,
            DR = 3,
            AC = 4,
            IR = 5,
            Memory = 7
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the Address Register.
        /// </summary>
        /// <value>The Address Register.</value>
        public Register AR { get; private set; }

        /// <summary>
        /// Gets the PC Register.
        /// </summary>
        /// <value>The PC Register.</value>
        public Register PC { get; private set; }

        /// <summary>
        /// Gets the Data Register.
        /// </summary>
        /// <value>The Data Register.</value>
        public Register DR { get; private set; }

        /// <summary>
        /// Gets the Accumulator.
        /// </summary>
        /// <value>The Accumulator.</value>
        public Register AC { get; private set; }

        /// <summary>
        /// Gets the Instruction Register.
        /// </summary>
        /// <value>The Instruction Register.</value>
        public Register IR { get; private set; }

        /// <summary>
        /// Gets the Sequence Counter.
        /// </summary>
        /// <value>The Sequence Counter.</value>
        public Register SequenceCounter { get; private set; }

        /// <summary>
        /// Gets the Memory.
        /// </summary>
        /// <value>The Memory.</value>
        public Memory Memory { get; private set; }

        /// <summary>
        /// Gets the Bus.
        /// </summary>
        /// <value>The Bus object.</value>
        public Bus Bus { get; private set; }

        /// <summary>
        /// Gets the ALU.
        /// </summary>
        /// <value>The ALU object.</value>
        public ALU ALU { get; private set; }

        /// <summary>
        /// Gets the Control Unit.
        /// </summary>
        /// <value>The Control Unit.</value>
        public ControlUnit ControlUnit { get; private set; }
        #endregion

        #region Methods
        /// <summary>
        /// Attaches the control signals.
        /// </summary>
        private void AttachControlSignals()
        {
            this.AC.Increment.AddSource(this.ControlUnit["IR0D3T3"]);

            this.AC.Load.AddSource(this.ControlUnit["D0T4"]);
            this.AC.Load.AddSource(this.ControlUnit["D1T4"]);
            this.AC.Load.AddSource(this.ControlUnit["IR1D3T3"]);

            this.ALU.And.AddSource(this.ControlUnit["D0T4"]);

            this.ALU.Decrement.AddSource(this.ControlUnit["IR1D3T3"]);

            this.ALU.Subtract.AddSource(this.ControlUnit["D1T4"]);

            this.AR.Load.AddSource(this.ControlUnit["T0"]);
            this.AR.Load.AddSource(this.ControlUnit["T2"]);

            this.Bus.GetSelector((int)BusLocations.AR).AddSource(this.ControlUnit["D2T3"]);

            this.Bus.GetSelector((int)BusLocations.IR).AddSource(this.ControlUnit["T2"]);

            this.Bus.GetSelector((int)BusLocations.Memory).AddSource(this.ControlUnit["D0T3"]);
            this.Bus.GetSelector((int)BusLocations.Memory).AddSource(this.ControlUnit["D1T3"]);
            this.Bus.GetSelector((int)BusLocations.Memory).AddSource(this.ControlUnit["T1"]);

            this.Bus.GetSelector((int)BusLocations.PC).AddSource(this.ControlUnit["T0"]);

            this.DR.Load.AddSource(this.ControlUnit["D0T3"]);
            this.DR.Load.AddSource(this.ControlUnit["D1T3"]);

            this.IR.Load.AddSource(this.ControlUnit["T1"]);

            this.Memory.Read.AddSource(this.ControlUnit["D0T3"]);
            this.Memory.Read.AddSource(this.ControlUnit["D1T3"]);
            this.Memory.Read.AddSource(this.ControlUnit["T1"]);

            this.PC.Increment.AddSource(this.ControlUnit["T1"]);

            this.PC.Load.AddSource(this.ControlUnit["D2T3"]);

            this.SequenceCounter.Clear.AddSource(this.ControlUnit["D0T4"]);
            this.SequenceCounter.Clear.AddSource(this.ControlUnit["D1T4"]);
            this.SequenceCounter.Clear.AddSource(this.ControlUnit["D2T3"]);
            this.SequenceCounter.Clear.AddSource(this.ControlUnit["IR0D3T3"]);
            this.SequenceCounter.Clear.AddSource(this.ControlUnit["IR1D3T3"]);
        }
        #endregion
    }
}
